/*   The MIT License
 *   
 *   Carina Engine
 *   Copyright (c) 2009 2010 2011 2012 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#include "carina/scene.hh"

#include "carina/resources.hh"
#include "carina/lighting.hh"
#include "carina/graphics-engine.hh"

namespace Carina
{
DrawableNode::DrawableNode(const DrawablePtr& drawable)
    :   m_Drawable(drawable)
{
}

DrawableNode::~DrawableNode()
{
}

void DrawableNode::draw(Scene& scene)
{
    m_Drawable->draw(scene, m_WorldMatrix);
}

void DrawableNode::notifyTransform(const Matrix4& wmat)
{
    m_WorldMatrix = wmat;
}

ConfigurablePtr DrawableNode::getConfigurable()
{
    return m_Drawable;
}

ConfigurableConstPtr DrawableNode::getConfigurable() const
{
    return m_Drawable;
}

UnlitDrawableNode::UnlitDrawableNode(const DrawablePtr& drawable)
    :   DrawableNode(drawable)
{
}

UnlitDrawableNode::~UnlitDrawableNode()
{
}

void UnlitDrawableNode::draw(Scene& scene)
{
    scene.pushUnlit(shared_from_this());
}

void UnlitDrawableNode::drawObject(Scene& scene)
{
    DrawableNode::draw(scene);
}

TransformNode::TransformNode()
{
    m_WorldMatrix.identity();
    m_LocalMatrix.identity();
}

TransformNode::TransformNode(const Matrix4& wmat, const Matrix4& lmat)
    :   m_WorldMatrix(wmat),
		m_LocalMatrix(lmat)
{
}

TransformNode::~TransformNode()
{
}

void TransformNode::draw(Scene& scene)
{
    for(size_t i = 0; i < m_Children.size(); ++i)
        m_Children[i]->draw(scene);
}

size_t TransformNode::getChildrenCount() const
{
    return m_Children.size();
}

NodePtr TransformNode::getChild(size_t idx)
{
    return m_Children[idx];
}

NodeConstPtr TransformNode::getChild(size_t idx) const
{
    return m_Children[idx];
}

void TransformNode::clear()
{
    m_Children.clear();
}

Matrix4 TransformNode::getWorldMatrix() const
{
    return m_WorldMatrix * m_LocalMatrix;
}

void TransformNode::setLocalMatrix(const Matrix4& mat)
{
    m_LocalMatrix = mat;
    for(size_t i = 0, iend = m_Children.size(); i < iend; ++i)
        m_Children[i]->notifyTransform(m_WorldMatrix * mat);
}

void TransformNode::multMatrix(const Matrix4& mat)
{
    m_LocalMatrix *= mat;
	for(size_t i = 0, iend = m_Children.size(); i < iend; ++i)
        m_Children[i]->notifyTransform(m_WorldMatrix*m_LocalMatrix);
}

void TransformNode::notifyTransform(const Matrix4& wm)
{
    m_WorldMatrix = wm;
    for(size_t i = 0, iend = m_Children.size(); i < iend; ++i)
        m_Children[i]->notifyTransform(m_WorldMatrix*m_LocalMatrix);
}

void TransformNode::addChild(const NodePtr& node)
{
    m_Children.push_back(node);
    m_Children.back()->notifyTransform(m_WorldMatrix*m_LocalMatrix);
}

void TransformNode::removeChildren(size_t lhs, size_t rhs)
{
    m_Children.erase(lhs, rhs);
}

void TransformNode::removeChild(size_t idx)
{
    m_Children.erase(idx);
}

ConfigurablePtr TransformNode::getConfigurable()
{
    return shared_from_this();
}

ConfigurableConstPtr TransformNode::getConfigurable() const
{
    return shared_from_this();
}

Scene::Scene(const GraphicsEngineContextPtr& ctx)
{
    init(ctx->getLightingManager(), ctx->getResourceManager(), ctx->getWindow()->getWidth(), ctx->getWindow()->getHeight(), ctx->getWindow()->getMSAA());
}

Scene::Scene(const LightingManagerPtr& lmgr, const ResourceManagerPtr& res_mgr, size_t w, size_t h, size_t samples)
{
    init(lmgr, res_mgr, w, h, samples);
}

void Scene::init(const LightingManagerPtr& lmgr, const ResourceManagerPtr& res_mgr, size_t w, size_t h, size_t samples)
{
    m_MSAA = samples;
    m_Root = make_aligned_shared<TransformNode>();

    m_Renderer = res_mgr->getRenderer();
    m_Camera = make_aligned_shared<Camera>(m_Renderer, 1.0f, 1000.0f, w, h);

#undef max
    m_GBuffer = m_Renderer->createFramebuffer();
    m_GBufferTextures.resize(GBUFFER_TEXTURES);
    resize(w, h);
    
    m_AmbBlendState = m_Renderer->createBlendStateObject(BlendStateDescription(true, CE_BLEND_SRC_ALPHA, CE_BLEND_INV_SRC_ALPHA, CE_BLEND_OP_ADD));
    m_BlendState = m_Renderer->createBlendStateObject(BlendStateDescription(true, CE_BLEND_SRC_ALPHA, CE_BLEND_ONE, CE_BLEND_OP_ADD, 
																	   CE_BLEND_ZERO, CE_BLEND_ZERO, CE_BLEND_OP_ADD));
    m_RenderingState = m_Renderer->createStateObject(MiscStateDescription(CE_FILL_SOLID, CE_CULL_BACK, 0.0f, 0.0f, true, false, false, false));
    m_LightingState = m_Renderer->createStateObject(MiscStateDescription(CE_FILL_SOLID, CE_CULL_NONE, 0.0f, 0.0f, false, false, false, false));
    m_DepthStencilState = m_Renderer->createDepthStencilStateObject(DepthStencilStateDescription(false, false));

    m_AmbientLight = static_pointer_cast<GlobalLightSource>(lmgr->create("Ambient light"));
}

void Scene::draw()
{
    FramebufferPtr draw_fb = m_Renderer->getDrawFramebuffer();

    Vector4 clear_color(m_Renderer->getClearColor());
    ScopedState rendering_state(m_Renderer, m_RenderingState);

    m_Renderer->setClearColor(Vector4(0.0f, 0.0f, 0.0f, 0.0f));
    m_Renderer->bindFramebuffer(m_GBuffer);

    m_Renderer->clearAll();

    m_Root->draw(*this);

    {
    m_Renderer->bindDrawFramebuffer(draw_fb);
    m_Renderer->blitFramebuffer(CE_DEPTH_BUFFER_BIT);

    ScopedState lighting_state(m_Renderer, m_LightingState);
    ScopedDepthStencilState depth_stencil_state(m_Renderer, m_DepthStencilState);

    m_Renderer->setClearColor(clear_color);

    {
    ScopedBlendState amb_bstate(m_Renderer, m_AmbBlendState);
    m_AmbientLight->render(*this, m_GBufferTextures[ALBEDO_TEXTURE].texture,
                                  m_GBufferTextures[NORMAL_VECTOR_TEXTURE].texture,
                                  m_GBufferDepthTexture.texture);
    }
    ScopedBlendState blend_state(m_Renderer, m_BlendState);

    for(size_t i = 0; i < m_LightSources.size(); ++i)
        m_LightSources[i]->render(*this,
                                  m_GBufferTextures[ALBEDO_TEXTURE].texture, 
                                  m_GBufferTextures[NORMAL_VECTOR_TEXTURE].texture,
                                  m_GBufferDepthTexture.texture);
    for(size_t i = 0; i < m_DLSources.size(); ++i)
        m_DLSources[i]->render(*this,
                               m_GBufferTextures[ALBEDO_TEXTURE].texture, 
                               m_GBufferTextures[NORMAL_VECTOR_TEXTURE].texture,
                               m_GBufferDepthTexture.texture);
    }

    m_LightSources.clear();

    while(!m_Unlit.empty())
    {
        m_Unlit.front()->drawObject(*this);
        m_Unlit.pop();
    }

    m_Renderer->bindFramebuffer(draw_fb);
}

void Scene::setAmbientLight(const Vector3& amb)
{
	static_cast<AmbientLight*>(m_AmbientLight.get())->setColor(amb);
}

Vector3 Scene::getAmbientLight() const
{
    return static_cast<AmbientLight*>(m_AmbientLight.get())->getColor();
}

void Scene::resize(size_t w, size_t h)
{
    m_GBufferTextures[ALBEDO_TEXTURE] = FBAttachmentInfo(m_Renderer->createTexture(TextureDescription(w, h, 1, VT_RGBA8_UNORM, m_MSAA), nullptr, 0));
    m_GBufferTextures[NORMAL_VECTOR_TEXTURE] = FBAttachmentInfo(m_Renderer->createTexture(TextureDescription(w, h, 1, VT_RGBA8_UNORM, m_MSAA), nullptr, 0));
    m_GBufferDepthTexture = FBAttachmentInfo(m_Renderer->createTexture(TextureDescription(w, h, 1, VT_D24S8, m_MSAA), nullptr, 0));

    m_GBuffer->attachDepthStencilTexture(m_GBufferDepthTexture);
    m_GBuffer->attachTextures(m_GBufferTextures);
    assert(m_GBuffer->validate());
    m_Camera->resize(w, h);
}

void Scene::addGlobalLightSource(const GlobalLightSourcePtr& dl)
{
    m_DLSources.push_back(dl);
}

void Scene::pushSpatialLightSource(const LightSourceNodePtr& ls)
{
    m_LightSources.push_back(ls);
}

void Scene::pushUnlit(const DrawRequestPtr& d)
{
    m_Unlit.push(d);
}

GlobalLightSourcePtr Scene::getGlobalLightSource(size_t idx)
{
    return m_DLSources[idx];
}

GlobalLightSourceConstPtr Scene::getGlobalLightSource(size_t idx) const
{
    return m_DLSources[idx];
}

size_t Scene::getGlobalLightSourceCount() const
{
    return m_DLSources.size();
}

void Scene::removeGlobalLightSource(size_t idx)
{
    m_DLSources.erase(idx);
}

void Scene::removeGlobalLightSources(size_t lhs, size_t rhs)
{
    m_DLSources.erase(lhs, rhs);
}

void Scene::clearGlobalLightSources()
{
    m_DLSources.clear();
}
}
